/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.model.codes;

import java.io.UnsupportedEncodingException;

import org.zhiqim.kernel.constants.CodeConstants;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Hexs;

/**
 * MD5实现算法，MD5采用小端(LittleEndian)位格式，即一个字节8位，低位在前，高位在后 算法操作
 * 1.填充，先对要对信息进行填充，使得字节长度为64字节的整数倍
 *   1)把原始字节传入，必须的；
 *   2)补位码(0x80)传入，必须的；比较长度是不是N*64+56，如果不是右补二进制0
 *   3)最后8字节输入长整型值，原始字节位数(字节长度*8)，必须的。
 * 2.把字节数据转换成整型数组，即16个整数的倍数。
 * 3.每组16个整数，循环进行更新数字签名。
 * 4.更新数字签名abcd(4个整数)算法为采用非线形函数循环4次位移得到值相加。
 * 5.对数字签名abcd(4个整数)生成128位字节(16字节)
 * 
 * @version v1.1.2 @author zouzhigang 2015-10-3 新建与整理
 */
public class MD5Coder implements CodeConstants
{
    /** 补位字节，第1位为1其他为0 */
    private static final byte PAD   = (byte)0x80;
    
    /** 4个初始化值 */
    private static final int[] V    = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};

    /** 4*4的矩阵 */
    private static final int[] S    = {7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21};

    /** 4*16次操作值 */
    private static final int[] T    =  {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 
                                        0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 
                                        0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 
                                        0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 
                                        0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 
                                        0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 
                                        0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 
                                        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};

    //实例初始化时，初值为固定的值
    private int[] abcd = {V[0], V[1], V[2], V[3]};
    
    /***************************************************************************************/
    // 以下为MD5实例实现方法 digest & update
    /***************************************************************************************/
    
    /**
     * 核心处理方法
     * 
     * @param bytes 传入的字节数组
     * @return      处理之后得到的MD5码
     */
    private byte[] digest(byte[] bytes)
    {
        //第一步，计算分组数目，每64字节一组，+8表示最后8个字节放原始字节长度，当bytes.length/64=0时要强制增加一组，其他情况补齐即可
        int num = ((bytes.length + 8) / 64) + 1;
        
        //第二步，按要求建立新的分组字节数组，分别把原始数据、补位码和原始数据大小按要求小端格式设置好
        byte[] buffer = new byte[64 * num];
        Bytes.putBytes(buffer, 0, bytes);
        Bytes.putByte(buffer, bytes.length, PAD);// 补位
        Bytes.LU.putLong(buffer, buffer.length - 8, bytes.length * 8);

        //第三步，把字节数组转换成整数
        int[] m = new int[16 * num];
        for (int i=0;i<16*num;i++){
            m[i] = Bytes.LU.getInt(buffer, i * 4);
        }
        buffer = null;

        //第四步，按每组16个整数(64字节)进行更新abcd的数字签名
        for (int i=0;i<num;i++){
            update(slice(m, i*16, (i+1)*16));
        }
        
        //第五步，把得到的abcd数字签名转化128位16个字节数组，返回结果
        byte[] result = new byte[16];
        Bytes.LU.putInt(result, 0, abcd[0]);
        Bytes.LU.putInt(result, 4, abcd[1]);
        Bytes.LU.putInt(result, 8, abcd[2]);
        Bytes.LU.putInt(result, 12, abcd[3]);
        return result;
    }
    
    /**
     * 分组更新abcd数字签名
     * 
     * @param x 一组16个整数(64字节)
     */
    private void update(int[] x)
    {
        int a = abcd[0], b = abcd[1], c = abcd[2], d = abcd[3];

        // 第一轮
        a = ff(a, b, c, d, x[0],  S[0],  T[0]);
        d = ff(d, a, b, c, x[1],  S[1],  T[1]);
        c = ff(c, d, a, b, x[2],  S[2],  T[2]);
        b = ff(b, c, d, a, x[3],  S[3],  T[3]);
        a = ff(a, b, c, d, x[4],  S[0],  T[4]);
        d = ff(d, a, b, c, x[5],  S[1],  T[5]);
        c = ff(c, d, a, b, x[6],  S[2],  T[6]);
        b = ff(b, c, d, a, x[7],  S[3],  T[7]);
        a = ff(a, b, c, d, x[8],  S[0],  T[8]);
        d = ff(d, a, b, c, x[9],  S[1],  T[9]);
        c = ff(c, d, a, b, x[10], S[2],  T[10]);
        b = ff(b, c, d, a, x[11], S[3],  T[11]);
        a = ff(a, b, c, d, x[12], S[0],  T[12]);
        d = ff(d, a, b, c, x[13], S[1],  T[13]);
        c = ff(c, d, a, b, x[14], S[2],  T[14]);
        b = ff(b, c, d, a, x[15], S[3],  T[15]);
        // 第二轮
        a = gg(a, b, c, d, x[1],  S[4],  T[16]);
        d = gg(d, a, b, c, x[6],  S[5],  T[17]);
        c = gg(c, d, a, b, x[11], S[6],  T[18]);
        b = gg(b, c, d, a, x[0],  S[7],  T[19]);
        a = gg(a, b, c, d, x[5],  S[4],  T[20]);
        d = gg(d, a, b, c, x[10], S[5],  T[21]);
        c = gg(c, d, a, b, x[15], S[6],  T[22]);
        b = gg(b, c, d, a, x[4],  S[7],  T[23]);
        a = gg(a, b, c, d, x[9],  S[4],  T[24]);
        d = gg(d, a, b, c, x[14], S[5],  T[25]);
        c = gg(c, d, a, b, x[3],  S[6],  T[26]);
        b = gg(b, c, d, a, x[8],  S[7],  T[27]);
        a = gg(a, b, c, d, x[13], S[4],  T[28]);
        d = gg(d, a, b, c, x[2],  S[5],  T[29]);
        c = gg(c, d, a, b, x[7],  S[6],  T[30]);
        b = gg(b, c, d, a, x[12], S[7],  T[31]);
        // 第三轮
        a = hh(a, b, c, d, x[5],  S[8],  T[32]);
        d = hh(d, a, b, c, x[8],  S[9],  T[33]);
        c = hh(c, d, a, b, x[11], S[10], T[34]);
        b = hh(b, c, d, a, x[14], S[11], T[35]);
        a = hh(a, b, c, d, x[1],  S[8],  T[36]);
        d = hh(d, a, b, c, x[4],  S[9],  T[37]);
        c = hh(c, d, a, b, x[7],  S[10], T[38]);
        b = hh(b, c, d, a, x[10], S[11], T[39]);
        a = hh(a, b, c, d, x[13], S[8],  T[40]);
        d = hh(d, a, b, c, x[0],  S[9],  T[41]);
        c = hh(c, d, a, b, x[3],  S[10], T[42]);
        b = hh(b, c, d, a, x[6],  S[11], T[43]);
        a = hh(a, b, c, d, x[9],  S[8],  T[44]);
        d = hh(d, a, b, c, x[12], S[9],  T[45]);
        c = hh(c, d, a, b, x[15], S[10], T[46]);
        b = hh(b, c, d, a, x[2],  S[11], T[47]);
        // 第四轮
        a = ii(a, b, c, d, x[0],  S[12], T[48]);
        d = ii(d, a, b, c, x[7],  S[13], T[49]);
        c = ii(c, d, a, b, x[14], S[14], T[50]);
        b = ii(b, c, d, a, x[5],  S[15], T[51]);
        a = ii(a, b, c, d, x[12], S[12], T[52]);
        d = ii(d, a, b, c, x[3],  S[13], T[53]);
        c = ii(c, d, a, b, x[10], S[14], T[54]);
        b = ii(b, c, d, a, x[1],  S[15], T[55]);
        a = ii(a, b, c, d, x[8],  S[12], T[56]);
        d = ii(d, a, b, c, x[15], S[13], T[57]);
        c = ii(c, d, a, b, x[6],  S[14], T[58]);
        b = ii(b, c, d, a, x[13], S[15], T[59]);
        a = ii(a, b, c, d, x[4],  S[12], T[60]);
        d = ii(d, a, b, c, x[11], S[13], T[61]);
        c = ii(c, d, a, b, x[2],  S[14], T[62]);
        b = ii(b, c, d, a, x[9],  S[15], T[63]);

        abcd[0] += a;
        abcd[1] += b;
        abcd[2] += c;
        abcd[3] += d;
    }
    
    /***************************************************************************************/
    // 以下为MD5更新数字签名的非线性函数 
    /***************************************************************************************/
    
    /** a = ((a + q + x + t) << s) + b */
    private static int cc(int q, int a, int b, int c, int d, int x, int s, int t)
    {
        a += q + x + t;
        a = (a << s) | (a >>> (32 - s));
        return a + b;
    }
    
    /** a = b + ((a + f(b,c,d) + x + t) << s) */
    private static int ff(int a, int b, int c, int d, int x, int s, int t)
    {
        return cc(f(b, c, d), a, b, c, d, x, s, t);
    }

    /** a = b + ((a + g(b,c,d) + x + t) << s) */
    private static int gg(int a, int b, int c, int d, int x, int s, int t)
    {
        return cc(g(b, c, d), a, b, c, d, x, s, t);
    }

    /** a = b + ((a + h(b,c,d) + x + t) << s) */
    private static int hh(int a, int b, int c, int d, int x, int s, int t)
    {
        return cc(h(b, c, d), a, b, c, d, x, s, t);
    }

    /** a = b + ((a + i(b,c,d) + x + t) << s) */
    private static int ii(int a, int b, int c, int d, int x, int s, int t)
    {
        return cc(i(b, c, d), a, b, c, d, x, s, t);
    }

    /** f(x, y, z) = (x & y) | ((~x) & z) */
    private static int f(int x, int y, int z)
    {
        return (x & y) | ((~x) & z);
    }

    /** g(x, y, z) = (x & z) | (y & (~z)) */
    private static int g(int x, int y, int z)
    {
        return (x & z) | (y & (~z));
    }

    /** h(x, y, z) = x ^ y ^ z */
    private static int h(int x, int y, int z)
    {
        return x ^ y ^ z;
    }

    /** i(x, y, z) = y ^ (x | (~z)) */
    private static int i(int x, int y, int z)
    {
        return y ^ (x | (~z));
    }
    
    /** 读取数组中指定起始和结束标志的新数组 */
    private static int[] slice(int[] m, int start, int end)
    {
        int len = end - start, index = 0;
        int[] x = new int[len];
        for (int i=start;i<end;i++) {
            x[index++] = m[i];
        }
        return x;
    }
    
    /***************************************************************************************/
    // 以下为MD5外部使用的方法
    /***************************************************************************************/
    
    /**
     * MD5 编码，UTF8
     * 
     * @param src 源串
     * @return 目标串
     */
    public static String encodeUTF8(String src)
    {
        return encode(src, _UTF_8_);
    }

    /**
     * MD5编码，指定encding
     * 
     * @param src 源串
     * @param encoding 编码
     * @return 目标串
     */
    public static String encode(String src, String encoding)
    {
        byte[] destBytes = encodeByte(src, encoding);
        return Hexs.toHexString(destBytes);
    }

    /**
     * MD5编码，指定encding
     * 
     * @param src 源串
     * @return 目标串
     */
    public static String encode(byte[] src)
    {
        byte[] destBytes = encodeByte(src);
        return Hexs.toHexString(destBytes);
    }

    /**
     * MD5编码,返回byte数组
     * 
     * @param src 源串
     * @return 目标编码
     */
    public static byte[] encodeByte(String src, String encoding)
    {
        Asserts.notNull(src, _SRC_);
        Asserts.notNull(encoding, _ENCODING_);
        
        try
        {
            return encodeByte(src.getBytes(encoding));
        }
        catch (UnsupportedEncodingException e)
        {
            throw Asserts.exception("非法的编码");
        }
    }

    /**
     * MD5编码,返回byte数组
     * 
     * @param src 源byte数组
     * @return 目标编码
     */
    public static byte[] encodeByte(byte[] src)
    {
        return new MD5Coder().digest(src);
    }
}
